package com.spring;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

public class MyApplicationContext {
    private Class<com.mywork.AppConfig> configClass;
    private ConcurrentHashMap<String,Object> singletonObjects=new ConcurrentHashMap<>();
    private ConcurrentHashMap<String,BeanDefinition> beanDefinitionMap=new ConcurrentHashMap<>();
    private List<BeanPostProcessor> beanPostProcessorList=new ArrayList<>();


    public MyApplicationContext(Class<com.mywork.AppConfig> configClass) throws UnsupportedEncodingException {
            this.configClass=configClass;
//          解析配置类
//        通过ComponentScan注解-》扫描路径-》扫描类-》解析类-》生成BeanDefinition->放入BeanDefinitionMap中
//        BeanDefinitionMap 中的key 是Component属性值 value 是BeanDefinition对象 该对象存放了对应的类的Class对象和Scope值
        scan(configClass);
        preInstantiateSingletons();  //实例化单例  ---》放到单例池中

    }

    private void preInstantiateSingletons() {
        //          把所有的单例BeanDefinition找出来 然后创建出所有的单例bean对象  放入到map中
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
//            如果是单例模式的  则创建对应的bean 并放在单例池中
            if (beanDefinition.getScopeValue().equals("singleton")) {
                Object bean = createBean(beanName,beanDefinition);
                singletonObjects.put(beanName,bean);
            }
        }
    }

    public Object createBean(String beanName,BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getClazz();
        try {
//            我们可以在这里判断类是否实现了InstantiationAwareBeanPostProcessor接口 如果实现了该接口那么在类实例化前后执行相应的方法：
//            注意 是实例化前后不是初始化前后
            Object o = clazz.getDeclaredConstructor().newInstance();
//            判断类里面是否有属性需要依赖注入
            Field[] declaredFields = clazz.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                if (declaredField.isAnnotationPresent(Autowired.class)) {
//                    里面有属性被Autowired修饰 将这个属性赋值
                    String name = declaredField.getName();
                    declaredField.setAccessible(true);
                    Object bean = getBean(name);

                    declaredField.set(o, bean);

                }
            }
//            赋值完成后 判断是否实现了BeanNameAware接口  并赋值
//            我们称之为 Aware回调
            if (o instanceof BeanNameAware){
                ((BeanNameAware) o).setBeanName(beanName);
            }
            //              BeanPostProcessor(bean的后置处理器)
//            我们可以利用这个后置处理器  在初始化前和 初始化后分别执行一些方法
            for (BeanPostProcessor processor : beanPostProcessorList) {
                 o = processor.postProcessBeforeInitialization(o, beanName);

            }
//            上述赋值完成后 判断是否实现了InitializingBean接口
//            我们称之为初始化
            if (o instanceof InitializingBean){
                ((InitializingBean) o).afterPropertiesSet();
            }
//              BeanPostProcessor(bean的后置处理器)
//            我们可以利用这个后置处理器  在初始化前和 初始化后分别执行一些方法
            for (BeanPostProcessor processor : beanPostProcessorList) {
                o = processor.postProcessAfterInitialization(o, beanName);
            }

            return o;
        } catch (InstantiationException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }


    }
    private void scan(Class<com.mywork.AppConfig> configClass) throws UnsupportedEncodingException {
        //          主要解析配置类上面的Spring定义的注解 而不会解析自定义的注解   这里模拟 解析spring包下的注解
//           1. 获取到配置类上的ComponentScan中的属性  拿到扫描路径
        ComponentScan annotation = (ComponentScan) configClass.getDeclaredAnnotation(ComponentScan.class);//(所有的注解都继承自Annotation)
        String scanPath = annotation.value();
        scanPath = scanPath.replaceAll("\\.", "/");
        System.out.println(scanPath);
//           2. 拿到扫描路径后获取到该路径下的所有文件
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(scanPath);


        assert resource != null;
        System.out.println(resource.getPath().substring(1));
        String decode = URLDecoder.decode(resource.getPath().substring(1), "UTF-8");
        File file = new File(decode);
//        3. 判断文件是否是文件夹
        if (file.isDirectory()){
//            获取文件夹下的所有文件
            File[] listFiles = file.listFiles();
            assert listFiles != null;
            for (File listFile : listFiles) {
                String fileName = listFile.getAbsolutePath();
                if (fileName.endsWith(".class")) {
//                如果是.class文件 则判断是否具有Component注解
                    String className = fileName.substring(fileName.indexOf("com"), fileName.lastIndexOf(".class"));
                    className = className.replace("\\", ".");
                    try {
                        Class<?> aClass = classLoader.loadClass(className);
                        if (aClass.isAnnotationPresent(Component.class)) {
                            // 如果有Component注解 则表示应该解析这个类
//                            如果这个类实现了BeanPostProcessor 表示Bean对象在实例化和依赖注入完毕后，在显示调用初始化方法的前后添加我们自己的逻辑。注意是Bean实例化完毕后及依赖注入完成后触发的
                            if (BeanPostProcessor.class.isAssignableFrom(aClass)){
                                BeanPostProcessor processorInstance = (BeanPostProcessor) aClass.getDeclaredConstructor().newInstance();
                                beanPostProcessorList.add(processorInstance);
                            }
//                            如果有Scope注解则将scope值添加到BeanDefinition中
                            BeanDefinition beanDefinition = new BeanDefinition();
//                            将上面生成的Class对象存入beanDefinition
                            beanDefinition.setClazz(aClass);
                            Component componentAnnotation = aClass.getDeclaredAnnotation(Component.class);
//                            获取到Component注解对应的value属性值
                            String value = componentAnnotation.value();

                            if (aClass.isAnnotationPresent(Scope.class)){
//                                    获取到Bean的Scope注解的值
                                    Scope scopeAnnotation = aClass.getDeclaredAnnotation(Scope.class);
                                    String scopeValue = scopeAnnotation.value();
                                    beanDefinition.setScopeValue(scopeValue);
                                }else {
//                                    没有获取到说明是单例模式 同样添加到map中
                                    beanDefinition.setScopeValue("singleton");
                            }
                            beanDefinitionMap.put(value,beanDefinition);
                        }

                    } catch (ClassNotFoundException e) {
                        throw new RuntimeException(e);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException(e);
                    } catch (InstantiationException e) {
                        throw new RuntimeException(e);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    }
                }

            }
        }
    }

    public Object getBean(String beanName){
        if (beanDefinitionMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if (beanDefinition.getScopeValue().equals("singleton")){
                Object o = singletonObjects.get(beanName);
                return o;
            }else {
//                创建bean对象
                Object bean = createBean(beanName,beanDefinition);
                return bean;
            }
        }else {
            throw new NullPointerException();
        }


    }
}
